<?php

include_once("core/normal.php");

 /** 
  * Mapped
  * 
  * This modelclass is useful when an object is directly linked with a entry in the table.
  * You can avoid to ever type a sql request for editing deleting and getting a row in the database with it.
  * See the examples and tutorials about that.
  *
  * Just make a class that inherits from it, and define the variable table to specify the table that you map.  
  * The important variables there are table and field. 
  *
  *  Range must be specified for mod 'c' and mod 'w'.  
  *  You can limit the range of an update and an insert with the range parameter.
  *
  * @copyright inovia
  * @package oolime_core           
  * @todo check behaviour of mapped with logging system and stability improvements             
  */
             
abstract class Mapped extends Normal {

  /**
   *@var object
   */
  protected $response;
  /**
   * @var object   
   */     
  protected $request;
  /**
   *@var object   
   */     
  protected $database;
  /**
   *@var object   
   */     
  protected $user;
  /**
   *@var object   
   */     
  protected $usergroup;
  
  /**
   * Put the name of the table that you want to map
   * @var object   
   */   
  public $table;
  
  /**
   *  You will retrieve and modify the fields of the mapped row with this variable.
   *  @var mixed   
   */     
  public $field;
  
  /**
   *  List of the fields that will be updated in the database at the destruction of the object. 
   *  Mainly useful when you want to partially update the database.
   *@var array
   */     
  public $range=Array();
  
  
  //a flag to signal a destruction
  private $delete=false;
  //the mod of opening
  private $mod;
  //useful to know if an entry exists...
  private $isOk=true;
  private $key;
  private $val;
  private $logs;
  
  /**
   * Constructs a mapped object, with multiple rights at the opening of the object
   * You must construct with a valid pair of key and value
   * The first param is the unique field in the table in an array : 
   *  {name => value}
   *  most part of the time name is an that's an id.
   *     
   *  Mod of entry opening :
  * [r] read only, no update will be done, no destruct available . SELECT...
  *  
  * [rw]  read and write. We can modify the fields and destruct the object. SELECT + UPDATE or DELETE...
  *       
  * [w] write only. You can't access the value of the row, but you can modify them. You must specify with range the name of the field to update. UPDATE or DELETE...
  *
  * [c] create only. INSERT...   
  * 
  * [l]  logic mode. There is no request done at all. Check the manual.
  *   
   *           
   * @param string mod "r" reading, "rw" modification and reading, "w" modification without load, "c" insert, "l" logic mode to avoid any request. 
   * @param string key the key to map 
   * @param string val the value of the key to map
   * @return object    
   */
   
  public function __construct($mod,$key="id",$val="null") {
    global $database;
    global $response;
    global $request;
    global $logs;
    global $user;
    global $usergroup;
    $this->response       = &$response;
    $this->request        = &$request;
    $this->database       = &$database;
    $this->user           = &$user;
    $this->usergroup      = &$usergroup;
    $this->logs           = &$logs;
    $this->delete= false;
      
    $this->mod=$mod;
    $this->key=$key;
    $this->val=$val;
    if ($mod=="w" OR $mod=="c"){
      $this->field->{$key}=$val;
      $this->range=array("id");
    }
    if($mod=="rw") $mods=" FOR UPDATE;";
    else $mods=";";
    
    if($mod=="r" OR $mod=="rw"){
      $req    = "SELECT * FROM `".$this->table."` 
                    WHERE `".$key."`=\"".$val."\" LIMIT 1".$mods;
      $result = $this->database->queries($req);
      if($result){
        $numbResult=$result->num_rows();
        if($numbResult==1) {
          $obj1         = $result->fetch_object();
          $this->field  = $obj1;
          $this->isOk=true;
        } 
        else {$this->isOk=false;}
      }else{$this->isOk=false;}
    }
    
  }
    
    
   /**
   * Call this method when you want to delete the mapped row.
   */ 
  public function delete(){
    $this->delete = true;
  }
  
  /**
   *Checks if the requested row exists in the database, and also checks if it is the only one to match the pair of key=>val.
   *Useful for mod r and rw, for any modification of object where the unique pair is not solid, this function must be called immediately after the constructor.   
   *@return bool
   */     
  public function isOk(){
    return $this->isOk;
  }



  /**
   * Maps the specified request values directly to the object fields.
   * Same as massively using request->get.       
   * Useful when you have a lot of values from a request to map without treatment on the object. Like when you retrieve a form.
   * you just pass an array of couples key => value, where key is the source name of the key in request table, and value is the destination name of the field. 
   * If there is any problem during the prcess, such as a name field that does'nt exist on the object, an error occurs.    
   * Look at an example to really understand the useness. 
   * @param array $tab the list of field that has to be mapped
   * @return bool a boolean that explain if the actions was okay or not.   
   */
  public function fromRequestStrict($tab){
    $flag = true;
    while(list($key,$val) = each($tab)) {
    
        if(!$this->field->{$val}=$this->request->get($key)){
          $this->logs->logError("Error on the mapping of key ".$key." with value ".$val);
          $flag=false;
        }
    }
    
    return $flag;
  }
   
   
  /**
   * Similar to fromRequestStrict with no error thrown if a problem occurs. 
   *          
   * @param array $tab the list of field that has to be mapped
   * @return bool a boolean that explain if the actions were okay or not.   
   */ 
  public function fromRequest($Atab){
  $flag = true;
      foreach($Atab as $key => $val) {
        if(!$this->field->{$val}=$this->request->get($key)){
          $flag=false;
        }
      }
      
      return $flag;
  }
  
  /**
   *Changes the mod of the object if possible.  
   *if the object was opened in read only, we can change it to readWrite and vice versa...
   *@param newMod the new opening mod of the object.   
   */        
  public function changeMod($newMod){
    if ($this->mod=="r" && $newMod=="rw") $this->mod="rw";
    if ($this->mod=="rw" && $newMod=="r") $this->mod="r";
    if ($this->mod=="w" && $newMod=="r") $this->mod="r";
    }
    
    
  /**
   *  Builds an XML node with the object.  
   * toXML is useful when you want to quickly build an answer in xml which describe your object.
   * You just pass a list of couples (object fields => xml node name) and the tree will be built.     
   * it's a two lines story and avoid a lot of DOM mess.
   *    
   * Look at an example to really understand the useness, it's very simple.    
   * @param array $tab the list of fields that has to be present in the XML, presented in key => val
   * @param string $fatherNode the name of the root of the node
   * @return DOMNode
   */
  
  public function toXML($fatherNode,$tab){
    $ParentNode = $this->response->doc->createElement($fatherNode);

    while(list($key,$val) = each($tab)) {
      try{
        $SonNode  = $this->response->doc->createElement($val);
        $valueSon = $this->response->doc->createTextNode($this->field->{$key});
        $SonNode->appendChild($valueSon);
        $ParentNode->appendChild($SonNode);
        
        $SonNode  = null;
        $valueSon = null; 
      }
      catch(Exception $e){
        $this->response->addError("probleme au mapping de key".$key." value ".$val);
      }
    }
    return $ParentNode;
  }
    
    
  /**
    * Destructor.
    * The resolution of the updates is done there. Here an update, delete or insert is launched.
    * @return string value of the key of the destructed object.      false if insert/delete/update failed
    * 
   */ 
  public function __destruct(){
      if($this->delete && $this->isOk && $this->mod=="rw" || $this->delete && $this->mod=="w"){
        $req = "DELETE FROM `".$this->table."` WHERE `".$this->key."`=\"".$this->field->{$this->key}."\";";
  		  $result = $this->database->queries($req);
  		  if($result){
    		  if($result->affected_rows() < 1) {
        	 	$this->retMessage("Entry not deleted.");
        	 	return false;
    		  } 
          else {
        		$this->retMessage("Entry deleted.");
        		return $this->field->{$this->key};
    		  }
  		  }else{
          $this->retMessage("Entry not deleted.");
        	return false;
        }
  		  
      }
      
      if(($this->mod=="rw" && $this->isOk) OR ($this->mod=="w" && (!($this->delete) && $this->key))){
          $req   = "UPDATE `".$this->table."` SET ";
          $str  = "";
          foreach($this->field as $key=>$val){
                $str.="`".$key."`=\"".$val."\",";
           }
           $req.= substr($str,0,strlen($str)-1)." WHERE `".$this->key."`=\"".$this->field->{$this->key}."\";";

      		$result = $this->database->queries($req);
          if(!$result){
            $this->response->addError("unable to update entry".$req);
        	 	return false;
          }
          return $this->field->{$this->key};
      }
      
      if($this->mod=="c" && !($this->delete)){
        
        $req="INSERT INTO ".$this->table." SET ";
        $str  = "";
        foreach($this->field as $key=>$val){
        if($val){
          $str.="`".$key."`=\"".$val."\",";
        }
        }
        $req.= substr($str,0,strlen($str)-1)." ;";
        $result = $this->database->queries($req);
        if($result){
        	$this->retMessage("Entry added.");
        	if ($this->key=="id") return $this->database->insert_id();
    		  else                  return $this->field->{$this->key};
  		  }else{
        	$this->retMessage("Entry NOT added.");
          $this->logs->logError("Unable to add entry : ".$req);
          return false; 
        }
      }
    if(!isset($this->field->{$this->key})) return null;
    return $this->field->{$this->key};
  }
  
}
?>
